local M = class("MsgEncoder")

function M:ctor(protos)
	self.protos = protos
end

MsgEncoder.encode = function(route, msg)
    --Get protos from protos map use the route as key
    local protos = self.protos[route]

    --Check msg
    if(not checkMsg(msg, protos)) then
        return nil
    end

    --Set the length of the buffer 2 times bigger to prevent overflow
    local length = codec.byteLength(JSON.stringify(msg))

    --Init buffer and offset
    local buffer = new ArrayBuffer(length)
    local uInt8Array = new Uint8Array(buffer)
    local offset = 0

    if(protos) then
        offset = encodeMsg(uInt8Array, offset, protos, msg)
        if(offset > 0) then
            return uInt8Array.subarray(0, offset)
        end
    end

    return nil
end


function checkMsg(msg, protos)
    if(not protos) then
        return false
    end

    for(local name in protos) do
        local proto = protos[name]

        --All required element must exist
        switch(proto.option)
            case 'required' :
                if(typeof(msg[name]) == 'undefined') then
                    return false
               	end
            case 'optional' :
                if(typeof(msg[name]) not == 'undefined')
                    if(protos.__messages[proto.type]) then
                        checkMsg(msg[name], protos.__messages[proto.type])
                    end
                end
                break
            case 'repeated' :
                --Check nest message in repeated elements
                if(msg[name] and protos.__messages[proto.type]) then
                    for(local i = 0 i < msg[name].length i++) do
                        if(not checkMsg(msg[name][i], protos.__messages[proto.type])) then
                            return false
                        end
                    end
                end
                break
        	end
    	end
	end

    return true
end

function encodeProp(value, type, offset, buffer, protos)
    switch(type)
        case 'uInt32':
            offset = writeBytes(buffer, offset, codec.encodeUInt32(value))
            break
        case 'int32' :
        case 'sInt32':
            offset = writeBytes(buffer, offset, codec.encodeSInt32(value))
            break
        case 'float':
            writeBytes(buffer, offset, codec.encodeFloat(value))
            offset += 4
            break
        case 'double':
            writeBytes(buffer, offset, codec.encodeDouble(value))
            offset += 8
            break
        case 'string':
            local length = codec.byteLength(value)

            --Encode length
            offset = writeBytes(buffer, offset, codec.encodeUInt32(length))
            --write string
            codec.encodeStr(buffer, offset, value)
            offset += length
            break
        default :
            if(protos.__messages[type])
                --Use a tmp buffer to build an internal msg
                local tmpBuffer = new ArrayBuffer(codec.byteLength(JSON.stringify(value)))
                local length = 0

                length = encodeMsg(tmpBuffer, length, protos.__messages[type], value)
                offset = writeBytes(buffer, offset, codec.encodeUInt32(length))
                for(local i = 0 i < length i++)
                    buffer[offset] = tmpBuffer[i]
                    offset++
                end
            end
            break
    	end

    return offset
end

function encodeMsg(buffer, offset, protos, msg)
    for(local name in msg)
        if(protos[name])
            local proto = protos[name]
            switch(proto.option)
                case 'required' :
                case 'optional' :
                    offset = writeBytes(buffer, offset, encodeTag(proto.type, proto.tag))
                    offset = encodeProp(msg[name], proto.type, offset, buffer, protos)
                    break
                case 'repeated' :
                    if(msg[name].length > 0)
                        offset = encodeArray(msg[name], proto, offset, buffer, protos)
                    
                    break
            
        		end
    	end
	end
    return offset
end

function encodeArray(array, proto, offset, buffer, protos)
    local i = 0

    if(util.isSimpleType(proto.type))
        offset = writeBytes(buffer, offset, encodeTag(proto.type, proto.tag))
        offset = writeBytes(buffer, offset, codec.encodeUInt32(array.length))
        for(i = 0 i < array.length i++) do
            offset = encodeProp(array[i], proto.type, offset, buffer)
        end
    else
        for(i = 0 i < array.length i++) do
            offset = writeBytes(buffer, offset, encodeTag(proto.type, proto.tag))
            offset = encodeProp(array[i], proto.type, offset, buffer, protos)
        end
    end

    return offset
end

function writeBytes(buffer, offset, bytes)
    for(local i = 0 i < bytes.length i++, offset++) do
        buffer[offset] = bytes[i]
    end

    return offset
end

function encodeTag(type, tag)
    local value = constant.TYPES[type] or 2
    return codec.encodeUInt32((tag<<3)|value)
end

return M

--(function (exports)
--
--    local protobuf = exports.Protobuf
--    local MsgEncoder = exports.Protobuf.encoder = 
--
--    local codec = protobuf.codec
--    local constant = protobuf.constants
--    local util = protobuf.util
--
--    MsgEncoder.init = function(protos)
--        self.protos = protos
--    
--
--    MsgEncoder.encode = function(route, msg)
--        --Get protos from protos map use the route as key
--        local protos = self.protos[route]
--
--        --Check msg
--        if(not checkMsg(msg, protos))
--            return nil
--        
--
--        --Set the length of the buffer 2 times bigger to prevent overflow
--        local length = codec.byteLength(JSON.stringify(msg))
--
--        --Init buffer and offset
--        local buffer = new ArrayBuffer(length)
--        local uInt8Array = new Uint8Array(buffer)
--        local offset = 0
--
--        if(protos)
--            offset = encodeMsg(uInt8Array, offset, protos, msg)
--            if(offset > 0)
--                return uInt8Array.subarray(0, offset)
--            
--        
--
--        return nil
--    
--
--    function checkMsg(msg, protos)
--        if(not protos)
--            return false
--        
--
--        for(local name in protos)
--            local proto = protos[name]
--
--            --All required element must exist
--            switch(proto.option)
--                case 'required' :
--                    if(typeof(msg[name]) == 'undefined')
--                        return false
--                    
--                case 'optional' :
--                    if(typeof(msg[name]) not == 'undefined')
--                        if(protos.__messages[proto.type])
--                            checkMsg(msg[name], protos.__messages[proto.type])
--                        
--                    
--                    break
--                case 'repeated' :
--                    --Check nest message in repeated elements
--                    if(msg[name] and protos.__messages[proto.type])
--                        for(local i = 0 i < msg[name].length i++)
--                            if(not checkMsg(msg[name][i], protos.__messages[proto.type]))
--                                return false
--                            
--                        
--                    
--                    break
--            
--        
--
--        return true
--    
--
--    function encodeMsg(buffer, offset, protos, msg)
--        for(local name in msg)
--            if(protos[name])
--                local proto = protos[name]
--                switch(proto.option)
--                    case 'required' :
--                    case 'optional' :
--                        offset = writeBytes(buffer, offset, encodeTag(proto.type, proto.tag))
--                        offset = encodeProp(msg[name], proto.type, offset, buffer, protos)
--                        break
--                    case 'repeated' :
--                        if(msg[name].length > 0)
--                            offset = encodeArray(msg[name], proto, offset, buffer, protos)
--                        
--                        break
--                
--            
--        
--
--        return offset
--    
--
--    function encodeProp(value, type, offset, buffer, protos)
--        switch(type)
--            case 'uInt32':
--                offset = writeBytes(buffer, offset, codec.encodeUInt32(value))
--                break
--            case 'int32' :
--            case 'sInt32':
--                offset = writeBytes(buffer, offset, codec.encodeSInt32(value))
--                break
--            case 'float':
--                writeBytes(buffer, offset, codec.encodeFloat(value))
--                offset += 4
--                break
--            case 'double':
--                writeBytes(buffer, offset, codec.encodeDouble(value))
--                offset += 8
--                break
--            case 'string':
--                local length = codec.byteLength(value)
--
--                --Encode length
--                offset = writeBytes(buffer, offset, codec.encodeUInt32(length))
--                --write string
--                codec.encodeStr(buffer, offset, value)
--                offset += length
--                break
--            default :
--                if(protos.__messages[type])
--                    --Use a tmp buffer to build an internal msg
--                    local tmpBuffer = new ArrayBuffer(codec.byteLength(JSON.stringify(value)))
--                    local length = 0
--
--                    length = encodeMsg(tmpBuffer, length, protos.__messages[type], value)
--                    offset = writeBytes(buffer, offset, codec.encodeUInt32(length))
--                    for(local i = 0 i < length i++)
--                        buffer[offset] = tmpBuffer[i]
--                        offset++
--                    
--                
--                break
--        
--
--        return offset
--    
--
--    function encodeArray(array, proto, offset, buffer, protos)
--        local i = 0
--
--        if(util.isSimpleType(proto.type))
--            offset = writeBytes(buffer, offset, encodeTag(proto.type, proto.tag))
--            offset = writeBytes(buffer, offset, codec.encodeUInt32(array.length))
--            for(i = 0 i < array.length i++)
--                offset = encodeProp(array[i], proto.type, offset, buffer)
--            
--        else
--            for(i = 0 i < array.length i++)
--                offset = writeBytes(buffer, offset, encodeTag(proto.type, proto.tag))
--                offset = encodeProp(array[i], proto.type, offset, buffer, protos)
--            
--        
--
--        return offset
--    
--
--    function writeBytes(buffer, offset, bytes)
--        for(local i = 0 i < bytes.length i++, offset++)
--            buffer[offset] = bytes[i]
--        
--
--        return offset
--    
--
--    function encodeTag(type, tag)
--        local value = constant.TYPES[type]or2
--        return codec.encodeUInt32((tag<<3)|value)
--    
--)(HY.net)